/*
===========================================================================
This source file is part of DyLab (Dynamic Laboratory)
For the latest info, see http://dylab.googlecode.com

Copyright (c) 2006-2008 Lukas Krejci
(krejci.lukas@volny.cz)

This file is part of DyLab.

    DyLab is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    DyLab is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with DyLab.  If not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/

//===========================================================================================
#include <fstream>
#include <boost/spirit/core.hpp>

//===========================================================================================
#include <dylab/utils/property/PropertyTree.h>
#include <dylab/utils/TokenParser.h>
#include <dylab/utils/containers/StackContainer.h>
#include <dylab/utils/exceptions/EMultipleOccurences.h>
#include <dylab/utils/exceptions/EOperationFailed.h>

//===========================================================================================
template <typename _ActionsT>
struct structured_properties 
    : public boost::spirit::grammar<structured_properties<_ActionsT> >
{
    _ActionsT actions;
    structured_properties(_ActionsT & _actions)
        : actions(_actions)
            { }

    template <typename ScannerT>
    struct definition
    {
        definition(structured_properties const& self)
        {
            _ActionsT const & actions = self.actions;
            line
                =   *boost::spirit::space_p 
                    >>  
                    *
                        (   
                            block_open[actions.blockOpen] |   
                            assignment[actions.propertyAssignment] |   
                            block_close[actions.blockClose]
                        ) 
                    >> 
                    *boost::spirit::space_p
                ;

            block_open
                =   boost::spirit::ch_p('{')
                ;

            block_close
                =   boost::spirit::ch_p('}')
                ;

            assignment 
                =       assignment_operand
                    >>  '=' 
                    >>  assignment_operand
                ;

            assignment_operand
                =   *
                        (   
                            boost::spirit::anychar_p - 
                            (
                                boost::spirit::ch_p('=') | 
                                boost::spirit::ch_p('{') |
                                boost::spirit::ch_p('}')
                            )
                        )
                ;
        }

        boost::spirit::rule<ScannerT> line, block_open, block_close, assignment, assignment_operand;

        boost::spirit::rule<ScannerT> const&
        start() const { return line; }
    };
};

//===========================================================================================
struct SPParserActions
{
    typedef dylab::PropertyTree::NodeList NodeList;
    typedef dylab::PropertyTree::NodeListMap NodeListMap;
    typedef dylab::PropertyTree::Node Node;

    // the property map stack
    typedef dylab::StackContainer<NodeListMap*> NodeListMapStack;

    struct BlockOpenAction
    {
        SPParserActions * parent;
        BlockOpenAction(SPParserActions * _parent)
            : parent(_parent)
                { }

        void operator()(char const * _first, char const * _last) const
        {
            dylab::String str(std::string(_first, _last));
            if (!str.isEmpty())
            {
                if (parent->lastProperty_NodeListMap == NULL)
                    throw dylab::EInvalidFormat(
						dylab::String::fmt("Opening bracket without parent property is not allowed. ('%1%', %2%)", 
							str, parent->fileName));
                if (!parent->propMapStack.isEmpty() && parent->propMapStack.top() == parent->currNodeListMap)
                    throw dylab::EInvalidFormat(
						dylab::String::fmt("Double opening bracket is not allowed. ('%1%', %2%)", 
							str, parent->fileName));

                parent->propMapStack.push(parent->currNodeListMap);        
                parent->currNodeListMap = parent->lastProperty_NodeListMap;
            }
        }
    };

    struct PropertyAssignmentAction
    {        
        SPParserActions * parent;
        PropertyAssignmentAction(SPParserActions * _parent)
            : parent(_parent)
                { }

        void operator()(char const * _first, char const * _last) const
        {
            dylab::String str(std::string(_first, _last));
            if (!str.isEmpty())
            {
                // create from the line new property and store it in the provided properties object
                dylab::Property prop(str.parse<dylab::Property>());
                if (!prop.getName().isEmpty())
                {   
                    // try to find the prop in the map
                    NodeList * propList = parent->currNodeListMap->find(prop.getName());

                    // property not found
                    if (propList == NULL)
                    {   // create new prop list and associate it with the new property name
                        propList = new NodeList();
                        parent->currNodeListMap->insert(prop.getName(), propList);
                    }

                    // create the property
                    Node * propNode = new Node(prop);
                    DYLAB_ASSERT(propNode != NULL);

                    // store the propety map of recently created property 
                    parent->lastProperty_NodeListMap = &(propNode->getChildren().getNodeListMap());

                    // insert the new property at the end of corresponding the property list
                    propList->insertAtEnd(propNode);                
                }
            }
        }
    };

    struct BlockCloseAction
    {
        SPParserActions * parent;
        BlockCloseAction(SPParserActions * _parent)
            : parent(_parent)
                { }

        void operator()(char const * _first, char const * _last) const
        {
            dylab::String str(std::string(_first, _last));
            if (!str.isEmpty())
            {
                if (parent->lastProperty_NodeListMap == NULL)
					throw dylab::EInvalidFormat(
						dylab::String::fmt("Closing bracket without previsou opening bracket is not allowed. ('%1%', %2%)", 
							str, parent->fileName));
                if (parent->propMapStack.isEmpty())
                    throw dylab::EInvalidFormat(
						dylab::String::fmt("Double closing bracket is not allowed. ('%1%', %2%)", 
							str, parent->fileName));

                parent->lastProperty_NodeListMap = parent->currNodeListMap;
                parent->currNodeListMap = parent->propMapStack.top();
                parent->propMapStack.pop();
            }
        }
    };

    BlockOpenAction             blockOpen;
    PropertyAssignmentAction    propertyAssignment;
    BlockCloseAction            blockClose;

    dylab::String                          fileName;
    dylab::PropertyTree *   sp;
    NodeListMap *                   currNodeListMap;
    NodeListMap *                   lastProperty_NodeListMap;
    NodeListMapStack                propMapStack;

#if defined(DYLAB_COMPILER_MSVC)
#       pragma warning (push)
#       pragma warning (disable : 4355)
#endif

    SPParserActions(const dylab::String & _fileName, dylab::PropertyTree * _sp)
        : fileName(_fileName), sp(_sp), blockOpen(this), propertyAssignment(this), blockClose(this)
            { 
                // store the pointer to the current property map
                currNodeListMap = &sp->getNodeListMap();

                // the last parsed property map
                lastProperty_NodeListMap = NULL;
            }

#if defined(DYLAB_COMPILER_MSVC)
#   pragma warning (pop)
#endif
};

//===========================================================================================
dylab::PropertyTree::PropertyTree()
{ 
}
//-----------------------------------------------------------------------------
dylab::PropertyTree::PropertyTree(const PropertyTree & _pt)
{
	// the property tree will be copied recursively using the copy method
	_pt.copy(*this);
}
//-----------------------------------------------------------------------------
dylab::PropertyTree::~PropertyTree()
{ 
}
//-----------------------------------------------------------------------------
dylab::PropertyTree::Node * 
dylab::PropertyTree::getNode(const String & _name)
{
    NodeList * propList = mNodeListMap.find(_name);

    if (propList == NULL)    
        throw EDoesNotExists(String::fmt("There are no properties named '%1%'.", _name));
    
    return propList->getFirst();    
}
//-----------------------------------------------------------------------------
const dylab::PropertyTree::Node * 
dylab::PropertyTree::getNode(const String & _name) const
{
    NodeList * propList = mNodeListMap.find(_name);

    if (propList == NULL)    
        throw EDoesNotExists(String::fmt("There are no properties named '%1%'.", _name));
    
    return propList->getFirst();    
}
//-----------------------------------------------------------------------------
dylab::PropertyTree::NodeList * 
dylab::PropertyTree::getNodeList(const String & _name)
{
    NodeList * propList = mNodeListMap.find(_name);

    if (propList == NULL)
        throw EDoesNotExists(String::fmt("There are no properties named '%1%'.", _name));

    return propList;
}
//-----------------------------------------------------------------------------
const dylab::PropertyTree::NodeList * 
dylab::PropertyTree::getNodeList(const String & _name) const
{
    NodeList * propList = mNodeListMap.find(_name);

    if (propList == NULL)
        throw EDoesNotExists(String::fmt("There are no properties named '%1%'.", _name));

    return propList;
}
//-----------------------------------------------------------------------------
dylab::PropertyTree::Node * dylab::PropertyTree::insertProperty(const Property & _property)
{
	// try to find the property in the map
	NodeList * propList = mNodeListMap.find(_property.getName());

	// property not found
	if (propList == NULL)
	{   // create new property list and associate it with the new property name
		propList = new NodeList();
		mNodeListMap.insert(_property.getName(), propList);
	}

	// create the property node
	Node * propNode = new Node(_property);
	DYLAB_ASSERT(propNode != NULL);

	// insert the new property at the end of corresponding the property list
	propList->insertAtEnd(propNode); 

	// return the created node
	return propNode;
}
//-----------------------------------------------------------------------------
void dylab::PropertyTree::destroyProperty(const String & _nodeName)
{
	// remove the node list with the specified name from the map
	mNodeListMap.remove(_nodeName);
}
//-----------------------------------------------------------------------------
void dylab::PropertyTree::copy(PropertyTree & _target) const
{
	// for all property lists
	for (NodeListMap::ConstIterator propList(mNodeListMap); propList.isValid(); propList++)
	{
		// for all properties in the list
		for (NodeList::ConstIterator propNode(*propList); propNode.isValid(); propNode++)
		{
			// copy property
			Node * insertedNode = _target.insertProperty(*propNode());

			// if it has any children nodes
			if (propNode->getChildren().getNodeListMap().getCount() > 0)	
			{
				// then continue the copy operation recursively on them
				propNode->getChildren().copy(insertedNode->getChildren());
			}
		}
	}
}
//-----------------------------------------------------------------------------
void dylab::PropertyTree::load(const String & _fileName)
{
    // open box definition text file
	std::ifstream f;	
	f.open(_fileName.c_str());
	if (f.fail())	        
        throw EOperationFailed(String::fmt("Can't open file: %1%", _fileName));		

    // parse the whole file
    SPParserActions spParserActions(_fileName, this);
    structured_properties<SPParserActions> parser(spParserActions);
	char buffer[256];
	while (f.getline(buffer, 256).gcount () > 0)	
    {
        String line = buffer;

        if (line.contains("//"))
            line = line.getFirstLeftPart("//");        
    
        line.trim();
        if (!line.isEmpty())
        {
            // parse the file content            
            boost::spirit::parse_info<> info = boost::spirit::parse(line.c_str(), parser, boost::spirit::space_p);

            if (!info.full)
				throw EOperationFailed(String::fmt("Parsing of file '%1%' failed. Stopped at '%2%', consumed %3% chars.", _fileName, info.stop, (uint32_t)info.length));
        }
    }
}
//-----------------------------------------------------------------------------